#!/usr/bin/env python
"""
##############################################################################
测试某一目录下的Python脚本，传入命令行参数，将stdin与管道相连，通过捕捉stdout、stderr和退
出状态来探测失败，以及获得前一次运行输出的回归记录。利用subprocess模块派生和控制流（类似Pyt
hon 2.X中的os.popen3），并可跨平台运行。流把它在subprocess中均为二进制字节。测试用到输入
、参数、输出、和错误均映射到子目录中的文件。

测试目录必须包含一个存放测试脚本的目录“Scripts”；如果有某个脚本需要获取命令行参数，则需在“A
rgs”目录下创建一个文件名为“除去.py后缀的原文件名 + .args”的文本文件；
如果需要输入，则需在“Inputs”目录下创建一个文件名为“除去.py后缀的原文件名 + .in”的文本文件；
“Errors”、“Outputs”等目录程序会自动创建。

这是一个命令行脚本，命令行参数均为可选，并不强制：第1个为测试的目录；若使用者输入参数“-f”或“
--forcegen”，则强制生成运行输出文件；若输入参数“-v”或“--verbose”，则显示详细运行情况。
##############################################################################
"""

import os
import sys
import time
import glob
from subprocess import Popen, PIPE


def verbose(*args):
    print('-' * 79)
    for arg in args:
        print(arg)


def quiet(*args):
    pass


# def getForcegen():
#     if len(sys.argv) > 2:
#         if sys.argv[2] == '1' or sys.argv[2] == '3':
#             return True
#         else:
#             return False
#     else:
#         return False


# def getTrace():
#     if len(sys.argv) > 2:
#         if sys.argv[2] == '2' or sys.argv[2] == '3':
#             return verbose
#         else:
#             return quiet
#     else:
#         return quiet


def pytester(
    testdir=sys.argv[1] if len(sys.argv) > 1 else os.curdir,
    forcegen='--forcegen' in sys.argv or '-f' in sys.argv,
    trace=verbose if '--verbose' in sys.argv or '-v' in sys.argv else quiet
):

    begintime = time.time()
    print('开始测试：', time.asctime(), sep='')
    print('在目录', os.path.abspath(testdir), '\n' * 3, sep='')

    # 创建文件夹
    errorsdirpath = os.path.join(testdir, 'Errors')
    outputsdirpath = os.path.join(testdir, 'Outputs')
    if not os.path.exists(errorsdirpath):
        os.mkdir(errorsdirpath)
    if not os.path.exists(outputsdirpath):
        os.mkdir(outputsdirpath)

    # 判断是否有“Args”、“Inputs”目录
    argsdirpath = os.path.join(testdir, 'Args')
    inputsdirpath = os.path.join(testdir, 'Inputs')
    haveargsdir = os.path.exists(argsdirpath)
    haveinputsdir = os.path.exists(inputsdirpath)

    # 得到需要测试的脚本
    testpatterns = os.path.join(testdir, 'Scripts', '*.py')
    # print(testpatterns)
    testfiles = glob.glob(testpatterns)
    testfiles.sort()
    # print(testfiles)
    trace(os.getcwd, *testfiles)

    numfail = 0
    for testpath in testfiles:
        testname = os.path.basename(testpath)
        filename = os.path.splitext(testname)[0]  # 去后缀

        # 若有，则导入命令行参数
        if haveargsdir:
            # print(argsdirpath)
            # print(filename + '.args')
            argspath = os.path.join(argsdirpath, filename + '.args')
            args = ''
            if os.path.exists(argspath):
                args = open(argspath, 'r').read().strip()

        # 若有，则打开输入文件，以供后续连接管道
        if haveinputsdir:
            inputspath = os.path.join(inputsdirpath, filename + '.in')
            inputs = b''
            if os.path.exists(inputspath):
                inputs = open(inputspath, 'rb').read()

        # 设置输出文件路径，并删除之前错误的输出文件
        outputspath = os.path.join(outputsdirpath, filename + '.out')
        badoutputspath = outputspath + '.bad'
        if os.path.exists(badoutputspath):
            os.remove(badoutputspath)

        # 设置保存错误的文件的路径，并删除之前保存错误的文件，打开文件
        errorspath = os.path.join(errorsdirpath, filename + '.err')
        if os.path.exists(errorspath):
            os.remove(errorspath)

        # 运行测试脚本，并重定向流
        pypath = sys.executable
        command = '{} {} {}'.format(pypath, testpath, args)
        trace(command, inputs)
        process = Popen(
            command, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE
        )
        process.stdin.write(inputs)
        process.stdin.close()
        outdata = process.stdout.read()
        errdata = process.stderr.read()
        exitcode = process.wait()
        trace(outdata, errdata, exitcode)

        # 分析测试结果
        if exitcode:
            print('程序返回值错误：', testname, exitcode, sep='')
        if errdata:
            open(errorspath, 'wb').write(errdata)
            print('程序运行错误：', testname, errorspath, sep='')

        if exitcode or errdata:
            numfail += 1
            open(badoutputspath, 'wb').write(outdata)
        elif not os.path.exists(outputspath) or forcegen:
            open(outputspath, 'wb').write(outdata)
            print('运行结果生成：', testname, outputspath, sep='')
        else:
            prioroutdata = open(outputspath, 'rb').read()
            if prioroutdata == outdata:
                print('通过：', testname, outputspath)
            else:
                numfail += 1
                open(badoutputspath, 'wb').write(outdata)
                print('运行结果错误：', testname, badoutputspath)
    finishtime = time.time()
    print('-' * 79)
    print('\n' * 3, '完成：', time.asctime(), sep='')
    print('用时：', finishtime - begintime)
    print('运行了{}个测试Python脚本，{}个测试失败。'.format(len(testfiles), numfail))


if __name__ == '__main__':
    pytester()
